/*
 *  $Id: Hint.cs 1189 2010-11-12 14:40:06Z thenn.erannor $
 *	This file is a part of Ragima CCG.
 *	(C) 2008-2009 Ragima development group.
 *
 *	Licensed under the terms of the GNU GPL License version 2.
 *
 */

using System;
using System.Collections.Generic;

using AgateLib.DisplayLib;
using AgateLib.Geometry;
using AgateLib.InputLib;

using Ragima.Helper;

namespace Ragima {
    /// <summary>
    /// Расположение подсказки относительно региона объекта
    /// </summary>
    public enum HintDirection {
        Left, Right, Top, Bottom
    }

    /// <summary>
    /// Подсказка по объекту
    /// </summary>
    public abstract class Hint {
        private static readonly Color DefaultBackgroundColor = 0x52504a.ToColor();
        private const int HintSpacing = ArrowHeight + 1;
        private const int ShadowInner = 3;
        private const int ShadowOuterLeft = 3;
        private const int ShadowOuterRight = 5;
        private const int ShadowOuterUp = 2;
        private const int ShadowOuterDown = 5;
        private const int BorderPadding = 1;
        private const int BorderLeftWidth = ShadowOuterLeft + 1 + ShadowInner + BorderPadding;
        private const int BorderRightWidth = BorderPadding + ShadowInner + 1 + ShadowOuterRight;
        private const int BorderUpHeight = ShadowOuterUp + 1 + ShadowInner + BorderPadding;
        private const int BorderDownHeight = BorderPadding + ShadowInner + 1 + ShadowOuterDown;
        private const int BorderLineLeftX = 17;
        private const int BorderLineRightX = 64;
        private const int BorderLineUpY = 18;
        private const int BorderLineDownY = 65;
        private static readonly Rectangle BorderUpLeft = new Rectangle(BorderLineLeftX - ShadowOuterLeft, BorderLineUpY - ShadowOuterUp, BorderLeftWidth, BorderUpHeight);
        private static readonly Rectangle BorderUpRight = new Rectangle(BorderLineRightX - ShadowInner, BorderLineUpY - ShadowOuterUp, BorderRightWidth, BorderUpHeight);
        private static readonly Rectangle BorderDownLeft = new Rectangle(BorderLineLeftX - ShadowOuterLeft, BorderLineDownY - ShadowInner, BorderLeftWidth, BorderDownHeight);
        private static readonly Rectangle BorderDownRight = new Rectangle(BorderLineRightX - ShadowInner, BorderLineDownY - ShadowInner, BorderRightWidth, BorderDownHeight);

        private const int ArrowWidth = 21;
        private const int ArrowHeight = 11;
        private const int ArrowVertX = 31;
        private const int ArrowVertWidth = ShadowOuterLeft + ArrowWidth + ShadowOuterRight;
        private const int ArrowVertXDiff = 10 + ShadowOuterLeft;
        private const int ArrowHorizY = 31;
        private const int ArrowHorizHeight = ShadowOuterUp + ArrowWidth + ShadowOuterDown;
        private const int ArrowHorizYDiff = 10 + ShadowOuterUp;
        private static readonly Rectangle ArrowLeftTemplate =
            new Rectangle(BorderLineLeftX - ArrowHeight - ShadowOuterLeft, ArrowHorizY - ShadowOuterUp, ArrowHeight + BorderLeftWidth, ArrowHorizHeight);
        private static readonly Rectangle ArrowRightTemplate =
            new Rectangle(BorderLineRightX - ShadowInner - BorderPadding, ArrowHorizY - ShadowOuterUp, ArrowHeight + BorderRightWidth, ArrowHorizHeight);
        private static readonly Rectangle ArrowUpTemplate =
            new Rectangle(ArrowVertX - ShadowOuterLeft, BorderLineUpY - ArrowHeight - ShadowOuterUp, ArrowVertWidth, ArrowHeight + BorderUpHeight);
        private static readonly Rectangle ArrowDownTemplate =
            new Rectangle(ArrowVertX - ShadowOuterLeft, BorderLineDownY - ShadowInner - BorderPadding, ArrowVertWidth, ArrowHeight + BorderDownHeight);
        private static readonly Rectangle[] ArrowTemplateRects = { ArrowLeftTemplate, ArrowRightTemplate, ArrowUpTemplate, ArrowDownTemplate };

        private const int BorderTemplateSize = 9;
        private static readonly Point BorderLineLeftTemplate = new Point(BorderLineLeftX - ShadowOuterLeft, BorderLineUpY + 1 + ShadowInner);
        private static readonly Point BorderLineRightTemplate = new Point(BorderLineRightX - ShadowInner, BorderLineUpY + 1 + ShadowInner);
        private static readonly Point BorderLineUpTemplate = new Point(BorderLineLeftX + 1 + ShadowInner, BorderLineUpY - ShadowOuterUp);
        private static readonly Point BorderLineDownTemplate = new Point(BorderLineLeftX + 1 + ShadowInner, BorderLineDownY - ShadowInner);
        private static readonly Rectangle[] BorderTemplateRects = {
            new Rectangle(BorderLineLeftTemplate, new Size(BorderLeftWidth, 0)),
            new Rectangle(BorderLineRightTemplate, new Size(BorderRightWidth, 0)),
            new Rectangle(BorderLineUpTemplate, new Size(0, BorderUpHeight)),
            new Rectangle(BorderLineDownTemplate, new Size(0, BorderDownHeight))
        };

        private Rectangle _hintRect, _content, _fullRect;
        private Color _backColor;
        private HintDirection _direction;
        private Rectangle[] _srcRects, _destRects;

        public Hint(Rectangle hintRect, Size size) {
            _hintRect = hintRect;
            Size fullSize = new Size(size.Width + BorderLeftWidth + BorderRightWidth, size.Height + BorderUpHeight + BorderDownHeight);
            _direction = CalcDirection(fullSize);
            if (IsVertical)
                size.Width = Math.Max(size.Width, ArrowVertWidth+2);
            else
                size.Height = Math.Max(size.Height, ArrowHorizHeight+2);
            fullSize = new Size(size.Width + BorderLeftWidth + BorderRightWidth, size.Height + BorderUpHeight + BorderDownHeight);
            Point position = CalcPosition(fullSize);
            _fullRect = new Rectangle(position, fullSize);
            position.X += BorderLeftWidth;
            position.Y += BorderUpHeight;
            _content = new Rectangle(position, size);
            _backColor = DefaultBackgroundColor;
            CalcBorder();
        }

        private HintDirection CalcDirection(Size size) {
            HintDirection dir = HintDirection.Top;
            int err = CalcPositionError(_hintRect.Bottom + HintSpacing, size.Height, Constants.DisplayHeight);
            if (err == 0) return dir;
            int err2 = CalcPositionError(_hintRect.Top - HintSpacing - size.Height, size.Height, Constants.DisplayHeight);
            if (err2 < err) {
                dir = HintDirection.Bottom;
                err = err2;
            }
            if (err == 0) return dir;
            err2 = CalcPositionError(_hintRect.Right + HintSpacing, size.Width, Constants.DisplayWidth);
            if (err2 < err) {
                dir = HintDirection.Left;
                err = err2;
            }
            if (err == 0) return dir;
            err2 = CalcPositionError(_hintRect.Left - HintSpacing - size.Width, size.Width, Constants.DisplayWidth);
            if (err2 < err) {
                dir = HintDirection.Right;
                err = err2;
            }
            return dir;
        }

        private bool IsVertical {
            get { return _direction == HintDirection.Top || _direction == HintDirection.Bottom; }
        }

        private Point CalcPosition(Size size) {
            int x, y;
            if (IsVertical) {
                y = (_direction == HintDirection.Top) ? _hintRect.Bottom + HintSpacing : _hintRect.Top - HintSpacing - size.Height;
                x = Math.Min(Math.Max(_hintRect.Left + (_hintRect.Width - size.Width)/2, 0), Constants.DisplayWidth - size.Width);
            } else {
                x = (_direction == HintDirection.Left) ? _hintRect.Right + HintSpacing : _hintRect.Left - HintSpacing - size.Width;
                y = Math.Min(Math.Max(_hintRect.Top + (_hintRect.Height - size.Height)/2, 0), Constants.DisplayHeight - size.Height);
            }
            return new Point(x, y);
        }
        private static int CalcPositionError(int pos, int len, int max) {
            int err = 0;
            if (pos < 0) err += -pos;
            if ((pos+len) > max) err += (pos+len) - max;
            return err*100 / len;
        }

        private void CalcBorder() {
            BorderAccumulator border = new BorderAccumulator();
            border.Add(BorderUpLeft, _fullRect.Location);
            border.Add(BorderUpRight, new Point(_content.Right, _fullRect.Top));
            border.Add(BorderDownLeft, new Point(_fullRect.Left, _content.Bottom));
            border.Add(BorderDownRight, new Point(_content.Right, _content.Bottom));
            CalcBorderLine(border, new Point(_content.Left, _fullRect.Top), HintDirection.Top);
            CalcBorderLine(border, new Point(_content.Left, _content.Bottom), HintDirection.Bottom);
            CalcBorderLine(border, new Point(_fullRect.Left, _content.Top), HintDirection.Left);
            CalcBorderLine(border, new Point(_content.Right, _content.Top), HintDirection.Right);
            Point arrow;
            if (IsVertical)
                arrow = new Point(_hintRect.Left + _hintRect.Width/2 - ArrowVertXDiff, _direction == HintDirection.Top ? _fullRect.Top - ArrowHeight : _content.Bottom);
            else
                arrow = new Point(_direction == HintDirection.Left ? _fullRect.Left - ArrowHeight : _content.Right, _hintRect.Top + _hintRect.Height/2 - ArrowHorizYDiff);
            border.Add(ArrowTemplateRects[(int) _direction], arrow);
            border.GetRects(out _srcRects, out _destRects);
        }
        private void CalcBorderLine(BorderAccumulator border, Point start, HintDirection dir) {
            bool vertical = (dir == HintDirection.Left || dir == HintDirection.Right);
            Point pos = start;
            int len = vertical ? _content.Height : _content.Width;
            Rectangle templateRect = BorderTemplateRects[(int) dir];
            while (len > 0) {
                int l = Math.Min(len, BorderTemplateSize);
                if (vertical)
                    templateRect.Height = l;
                else
                    templateRect.Width = l;
                border.Add(templateRect, pos);
                if (vertical)
                    pos.Y += l;
                else
                    pos.X += l;
                len -= l;
            }
        }

        public Rectangle HintRect { get { return _hintRect; } }
        public int ContentWidth { get { return _content.Width; } }
        public int ContentHeight { get { return _content.Height; } }
        public Color BackgroundColor { get { return _backColor; } set { _backColor = value; } }

        public void Draw() {
            Display.FillRect(_content, _backColor);
            DrawBorder();
            DrawContent(_content);
        }

        private void DrawBorder() {
            Data.Hint.DrawRects(_srcRects, _destRects);
        }

        protected abstract void DrawContent(Rectangle rect);

        public virtual void Process() {}

        private class BorderAccumulator {
            private List<Rectangle> _src;
            private List<Point> _dest;

            public BorderAccumulator() {
                _src = new List<Rectangle>();
                _dest = new List<Point>();
            }

            public void Add(Rectangle src, Point dest) {
                _src.Add(src);
                _dest.Add(dest);
            }

            public void GetRects(out Rectangle[] src, out Rectangle[] dest) {
                int count = _src.Count;
                src = new Rectangle[count];
                dest = new Rectangle[count];
                for (int i = 0; i < count; i++) {
                    src[i] = _src[i];
                    dest[i] = new Rectangle(_dest[i], _src[i].Size);
                }
            }
        }
    }

    public class TextHint: Hint {
        private const int TextMinWidth = 50;
        private const int TextMaxWidth = Constants.DisplayWidth / 3;
        private const int LineHeight = 16;

        private List<string> _lines;

        public TextHint(Rectangle hintRect, string text): base(hintRect, CalcSize(text)) {
            _lines = new List<string>(Data.Fonts.Notice.Wrap(text, ContentWidth));
        }

        private static Size CalcSize(string text) {
            int width = TextMinWidth, count = 0;
            foreach (string s in Data.Fonts.Notice.Wrap(text, TextMaxWidth)) {
                width = Math.Max(width, Data.Fonts.Notice.GetWidth(s));
                count++;
            }
            return new Size(width, count*LineHeight);
        }

        protected override void DrawContent(Rectangle rect) {
            int y = rect.Y;
            Data.Fonts.Notice.Color = Color.Black;
            foreach (string s in _lines) {
                Data.Fonts.Notice.Draw(rect.X, y, s);
                y += LineHeight;
            }
        }
    }
}
